/*
 * Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <errno.h>
#include <modbus.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <signal.h>
#include "ipc_socket.h"
#include "modbus_ipc.h"

#define SERVER_ID 17
#define TIMER_INIT 1
#define TIMER_REPEAT 5

#define READ_REGISTERS_MAX 125

enum {
    TCP,
    TCP_PI,
    RTU,
    RS485
};

#define REGISTERS_SIMULATOR_START 1000
#define REGISTERS_SIMULATOR_END 1078
#define REGISTERS_SIMULATOR_NUM (REGISTERS_SIMULATOR_END - REGISTERS_SIMULATOR_START + 1)

#define REGISTERS_STATUS_START 1100
#define REGISTERS_STATUS_END 1107
#define REGISTERS_STATUS_NUM (REGISTERS_STATUS_END - REGISTERS_STATUS_START + 1)

#define REGISTERS_SYS1_START 3503
#define REGISTERS_SYS1_END 3594
#define REGISTERS_SYS1_NUM (REGISTERS_SYS1_END - REGISTERS_SYS1_START + 1)

#define REGISTERS_SYS2_START 6001
#define REGISTERS_SYS2_END 6043
#define REGISTERS_SYS2_NUM (REGISTERS_SYS2_END - REGISTERS_SYS2_START + 1)

#define REGISTERS_TIME_START 7501
#define REGISTERS_TIME_END 7506
#define REGISTERS_TIME_NUM (REGISTERS_TIME_END - REGISTERS_TIME_START + 1)

#define REGISTERS_CONTROL_START 8000
#define REGISTERS_CONTROL_END 8002
#define REGISTERS_CONTROL_NUM (REGISTERS_CONTROL_END - REGISTERS_CONTROL_START + 1)

#define REGISTERS_DEVICE_START 8100
#define REGISTERS_DEVICE_END 8100
#define REGISTERS_DEVICE_NUM (REGISTERS_DEVICE_START - REGISTERS_DEVICE_END + 1)

typedef struct{
    int registers_start;
    int registers_end;
    int registers_type;
    uint16_t *dataNew;
}modbus_registers_range;

static modbus_registers_range modbusRegisters[] = {
    {REGISTERS_SIMULATOR_START, REGISTERS_SIMULATOR_END, MODBUS_READ, NULL},
    {REGISTERS_STATUS_START   , REGISTERS_STATUS_END   , MODBUS_READ, NULL},
    {REGISTERS_SYS1_START     , REGISTERS_SYS1_END     , MODBUS_READ | MODBUS_WRITE, NULL},
    {REGISTERS_SYS2_START     , REGISTERS_SYS2_END     , MODBUS_READ | MODBUS_WRITE, NULL},
    {REGISTERS_TIME_START     , REGISTERS_TIME_END     , MODBUS_WRITE, NULL},
    {REGISTERS_DEVICE_START   , REGISTERS_DEVICE_END   , MODBUS_WRITE, NULL}
};

static modbus_t *ctx = NULL;
//初始化寄存器
static void init_registers(void)
{
    int arrayNum = 0;
    int i = 0;
    int spaceSize = 0;
    arrayNum = sizeof(modbusRegisters) / sizeof(modbus_registers_range);

    for (i = 0; i <arrayNum; i++) {
        spaceSize = modbusRegisters[i].registers_end - modbusRegisters[i].registers_start + 1;
        spaceSize = spaceSize * sizeof(uint16_t);
        modbusRegisters[i].dataNew = (uint16_t *)malloc(spaceSize);
        memset(modbusRegisters[i].dataNew, 0, spaceSize);
    }
    return;
}

//初始化寄存器
static void uninit_registers(void)
{
    int arrayNum = 0;
    int i = 0;
    arrayNum = sizeof(modbusRegisters) / sizeof(modbus_registers_range);

    for (i = 0; i <arrayNum; i++) {
        free(modbusRegisters[i].dataNew);
        modbusRegisters[i].dataNew = NULL;
    }
    return;
}

//若满足地址的权限要求，返回对应寄存器的地址指针
static uint16_t* registers_permission_check(int addr, int type)
{
    int optType = MODBUS_NONE;
    int arrayNum = 0;
    int i = 0;
    int addr_start = 0;
    int addr_end = 0;
    uint16_t *dataPtr = NULL;
    arrayNum = sizeof(modbusRegisters) / sizeof(modbus_registers_range);

    for (i = 0; i <arrayNum; i++) {
        optType = modbusRegisters[i].registers_type;
        
        if ((optType & type) && (modbusRegisters[i].dataNew != NULL)) {
            addr_start = modbusRegisters[i].registers_start;
            addr_end = modbusRegisters[i].registers_end;
            if ((addr < addr_start) || (addr > addr_end)) {
                continue;
            }
            dataPtr = &modbusRegisters[i].dataNew[addr - addr_start];
#ifdef DEBUG
            int j = 0;
            for (j = 0; j < (addr_end - addr_start + 1); j++) {
                if ((j % 10) == 0) {
                    printf("\n");
                }
                printf("%d\t", modbusRegisters[i].dataNew[j]);
            }
            printf("\n");
            printf("Warning: Registers Start = %d End = %d\n", addr_start, addr_end);
            printf("Warning: Registers Type = %d Addr = %d Data = %d\n", type, addr, modbusRegisters[i].dataNew[addr - addr_start]);
#endif
            break;
        }
    }
    return dataPtr;
}

static void modbus_registers_deal(modbus_common *modbusData)
{
    uint16_t *dataPtr = NULL;
    int type = MODBUS_NONE;
    modbus_common modbusRegister;
    if (modbusData == NULL) {
        return;
    }
    type = modbusData->type;
    if ((type <= MODBUS_NONE) || (type >= MODBUS_END)) {
        return;
    }
    dataPtr = registers_permission_check(modbusData->addr, modbusData->type);
    if (dataPtr == NULL) {
#ifdef DEBUG
        printf("Warning: Out the range or permission\n");
#endif
        return;
    }

    switch (modbusData->type) {
        case MODBUS_READ:
            modbusRegister.type = MODBUS_UPDATE;
            modbusRegister.addr = modbusData->addr;
            modbusRegister.value = (int)*dataPtr;
#ifdef DEBUG
            printf("IPC Read:Type = %d Addr = %d Data = %d\n", modbusRegister.type, modbusRegister.addr, modbusRegister.value);
#endif
            send_TLV_data(MODBUS_IPC, &modbusRegister, sizeof(modbus_common));
            break;
        case MODBUS_WRITE:
        case MODBUS_UPDATE:
            modbus_write_register(ctx, modbusData->addr, (uint16_t)modbusData->value);
            break;
        default:
            break;
    }
}

static void recv_modbus_data(void)
{
    char *dataPtr = NULL;
    modbus_common *modbusData = NULL;
    int size = 0;
    int recvLen = 0;

    size = sizeof(modbus_common);
    dataPtr = (char *)malloc(size);
    memset(dataPtr, 0, size);
    //处理MODBUS的TLV数据
    recvLen = get_TLV_data(MODBUS_IPC, (void *)dataPtr, size);
    while(recvLen) {
        if (recvLen >= sizeof(modbus_common)) {
            modbusData = (modbus_common *)dataPtr;
#ifdef DEBUG
            printf("Type = %d Addr = %d Data = %d\n", modbusData->type, modbusData->addr, modbusData->value);
#endif
            modbus_registers_deal(modbusData);
        }
        memset(dataPtr, 0, size);
        recvLen = get_TLV_data(MODBUS_IPC, (void *)dataPtr, size);
    }
    free(dataPtr);
    dataPtr = NULL;
}

//设置定时器
static void setup_timer(int seconds, int repeat) {
    struct itimerval new_value, old_value;
    // 设置定时器初始值
    new_value.it_value.tv_sec = seconds;
    new_value.it_value.tv_usec = 0;
    // 设置定时器停止后的状态，0表示不重复
    new_value.it_interval.tv_sec = repeat;
    new_value.it_interval.tv_usec = 0;
    // 设置定时器类型为ITIMER_REAL
    setitimer(ITIMER_REAL, &new_value, &old_value);
}
//modbus寄存器比对
static void modbus_register_cmp(int index, uint16_t *newData, int offset, int num)
{
    int size = 0;
    int i = 0;
    int addr_start = 0;
    modbus_common sendData;
    size = modbusRegisters[index].registers_end - modbusRegisters[index].registers_start + 1;
    if (offset + num > size) {
#ifdef DEBUG
        printf("ERROR:compare out of range\n");
#endif
        return;
    }
    //寄存器缓存区起始地址
    addr_start = modbusRegisters[index].registers_start + offset;
    for (i = 0; i < num; i++) {
        if (modbusRegisters[index].dataNew[offset + i] != newData[i]) {
#ifdef DEBUG
            printf("Update:addr=%d new(%d)==>old(%d)\n", addr_start + i, newData[i], modbusRegisters[index].dataNew[offset + i]);
#endif
            modbusRegisters[index].dataNew[offset + i] = newData[i];

            sendData.type = MODBUS_UPDATE;
            sendData.addr = addr_start + i;
            sendData.value = newData[i];
            send_TLV_data(MODBUS_IPC, (void *)&sendData, sizeof(modbus_common));
        }
    }
}
//定时器处理函数
static void timer_signal_handler(int sig)
{
    unsigned int arrayNum = 0;
    unsigned int i = 0;
    unsigned int registerNum = 0;
    int readNum = 0;
    unsigned int addrBegin = 0;
    unsigned int addrOffset = 0;
    uint16_t *dataPtr = NULL;
    arrayNum = sizeof(modbusRegisters) / sizeof(modbus_registers_range);
    if (sig == SIGALRM) {
        dataPtr = (uint16_t *)malloc(READ_REGISTERS_MAX * sizeof(uint16_t));

        for (i = 0; i <arrayNum; i++) {
            registerNum = modbusRegisters[i].registers_end - modbusRegisters[i].registers_start + 1;
            addrBegin = modbusRegisters[i].registers_start;
            addrOffset = 0;
            while(registerNum) {
                if (registerNum > READ_REGISTERS_MAX) {
                    readNum = READ_REGISTERS_MAX;
                } else {
                    readNum = registerNum;
                }
#ifdef DEBUG
                printf("Read modbus registers: addr=%04d num=%04d offset=%04d\n", addrBegin, readNum, addrOffset);
#endif
                memset(dataPtr, 0, READ_REGISTERS_MAX * sizeof(uint16_t));
                readNum = modbus_read_registers(ctx, addrBegin, readNum, dataPtr);
                if (readNum == -1) {
#ifdef DEBUG
                printf("ERROR: Read error return %d\n", readNum);
#endif
                    break;
                }
                //对比新旧数据
                modbus_register_cmp(i, dataPtr, addrOffset, readNum);

                addrBegin += readNum;
                registerNum -= readNum;
                addrOffset += readNum;
            }
        }
        free(dataPtr);
        dataPtr = NULL;
    }
}

static void *modify_register_handler(void *param)
{
    int addr = 0;
    int value = 0;
    int opt = 0;
    int rc = 0;
    while(is_exit()) {
        printf("Plese input addr:");
        scanf("%d", &addr);
        printf("Read[%d] or write[%d]:", MODBUS_READ, MODBUS_WRITE);
        scanf("%d", &opt);
        if (opt == MODBUS_WRITE) {
            printf("Please input Value:");
            scanf("%d", &value);
        }
        if (opt == MODBUS_READ) {
            rc = modbus_read_registers(ctx, addr, 1, (uint16_t *)&value);
        } else {
            rc = modbus_write_register(ctx, addr, (uint16_t)value);
        }
        printf("addr:%d value:%d\n", addr, value);
        if (rc == 1) {
            printf("OK\n");
        } else {
            printf("ERROR\n");
        }
    }
    printf("Exit modify register thread\n");
    pthread_exit(&rc);
}

static pthread_t modify_thread;
static int modify_register_thread(void)
{
    int ret = 0;
    ret = pthread_create(&modify_thread, NULL, modify_register_handler, (void *)(&ret));
    if(ret != 0) {
        perror("thread create");
        ret = -1;
    }
    return ret;
}

int main(int argc, char *argv[])
{
    uint32_t old_response_to_sec;
    uint32_t old_response_to_usec;

    int use_backend;
    char *ip_or_device;

    int fd = -1;
    pthread_t thread_socket;

    if (argc > 1) {
        if (strcmp(argv[1], "tcp") == 0) {
            use_backend = TCP;
        } else if (strcmp(argv[1], "tcppi") == 0) {
            use_backend = TCP_PI;
        } else if (strcmp(argv[1], "rtu") == 0) {
            use_backend = RTU;
        } else if (strcmp(argv[1], "rs485") == 0) {
            use_backend = RS485;
        } else {
            printf("Modbus client for unit testing\n");
            printf("Usage:\n  %s [tcp|tcppi|rtu]\n", argv[0]);
            printf("Eg. tcp 127.0.0.1 or rtu /dev/ttyUSB1\n\n");
            exit(1);
        }
    } else {
        /* By default */
        use_backend = TCP;
    }

    if (argc > 2) {
        ip_or_device = argv[2];
    } else {
        switch (use_backend) {
        case TCP:
            ip_or_device = "127.0.0.1";
            break;
        case TCP_PI:
            ip_or_device = "::1";
            break;
        case RTU:
            ip_or_device = argv[2];
        case RS485:
            ip_or_device = argv[2];
            break;
        default:
            break;
        }
    }

    if (use_backend == TCP) {
        ctx = modbus_new_tcp(ip_or_device, 1502);
    } else if (use_backend == TCP_PI) {
        ctx = modbus_new_tcp_pi(ip_or_device, "1502");
    } else if (use_backend == RTU) {
        ctx = modbus_new_rtu(ip_or_device, 9600, 'N', 8, 1);
    }  else if (use_backend == RS485) {
        ctx = modbus_new_rtu_rs485(ip_or_device, 9600, 'N', 8, 1);
    }
    if (ctx == NULL) {
        fprintf(stderr, "Unable to allocate libmodbus context\n");
        return -1;
    }

    //modbus_set_debug(ctx, TRUE);
    modbus_set_error_recovery(ctx, MODBUS_ERROR_RECOVERY_LINK | MODBUS_ERROR_RECOVERY_PROTOCOL);

    if ((use_backend == RTU) || (use_backend == RS485)) {
        modbus_set_slave(ctx, SERVER_ID);
    }

    modbus_get_response_timeout(ctx, &old_response_to_sec, &old_response_to_usec);
    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    
    fd = client_socket_init();
    if(fd == -1) {
        perror("Can't create socket or connect");
    }
    
    if((fd != -1) && (socket_recv_thread(&thread_socket, fd) == -1)) {
        perror("Create thread failed");
        close(fd);
        fd = -1;
    }

    if ((fd != -1) && (socketfd_bind(MODBUS_IPC, fd) != fd)) {
        perror("Socket fd bind failed\n");
    }
    //初始化一个线程，发送寄存器修改指令
    //modify_register_thread();
    //初始化定时器
    setup_timer(TIMER_INIT, TIMER_REPEAT);
    signal(SIGALRM,timer_signal_handler);
    init_registers();
    while(is_exit()) {   
        recv_modbus_data();
    }
    uninit_registers();
    socketfd_unbind(MODBUS_IPC);
    close(fd);
    return 0;
}